﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading;

namespace ORMAC.Reflection
{
    public static class ReflectionCacheManager
    {
        private static Dictionary<Type,Dictionary<string,List<Attribute>>> propertyCustomeAttributes=new Dictionary<Type,Dictionary<string,List<Attribute>>>();
        private static Dictionary<Type, List<Attribute>> classCustomeAttributes = new Dictionary<Type, List<Attribute>>();
        private static Dictionary<Type, List<PropertyInfo>> classProperties = new Dictionary<Type,List<PropertyInfo>>();
        private static Dictionary<Type, List<string>> classPrimaryKeys = new Dictionary<Type, List<string>>();
        private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();

        public static List<PropertyInfo> GetProperties<T>()
        {
            return GetProperties(typeof(T));
        }

        public static List<PropertyInfo> GetProperties(Type classType)
        {
            List<PropertyInfo> res = null;
            try
            {
                rwLock.EnterReadLock();
                if (!classProperties.ContainsKey(classType))
                {
                    rwLock.ExitReadLock();
                    AddClassProperties(classType);
                    rwLock.EnterReadLock();
                }
                if (classProperties.ContainsKey(classType))
                {
                    res = classProperties[classType];
                }
            }
            finally
            {
                rwLock.ExitReadLock();
            }
            return res;
        }

        private static void AddClassProperties(Type classType)
        {
            try
            {
                rwLock.EnterWriteLock();
                var properties = classType.GetProperties();
                classProperties.Add(classType, new List<PropertyInfo>());
                if(properties!=null)
                    classProperties[classType].AddRange(properties);
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
        }
        
        public static List<Attribute> GetCustomAttributes<T>(Type classType, string propertyName)
        {
            return GetCustomAttributes(classType, propertyName, typeof(T));
        }
        public static List<Attribute> GetCustomAttributes<T>(Type classType)
        {
            return GetCustomAttributes(classType,typeof(T));
        }

        private static List<Attribute> GetCustomAttributes(Type classType, Type attributeType)
        {
            List<Attribute> res = null;
            try
            {
                rwLock.EnterReadLock();
                if (!classCustomeAttributes.ContainsKey(classType))
                {
                    rwLock.ExitReadLock();
                    AddClassCustomAttributes(classType);
                    rwLock.EnterReadLock();
                }
                if (classCustomeAttributes.ContainsKey(classType))
                {
                    res = classCustomeAttributes[classType].FindAll(a => a.GetType() == attributeType);
                }
            }
            finally
            {
                rwLock.ExitReadLock();
            }
            return res;
        }

        private static void AddClassCustomAttributes(Type classType)
        {
            try
            {
                rwLock.EnterWriteLock();
                var attribs=classType.GetCustomAttributes(false);
                classCustomeAttributes.Add(classType, new List<Attribute>());
                if (attribs != null)
                {
                    foreach (var a in attribs)
                        classCustomeAttributes[classType].Add(a as Attribute);
                }
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
        }
        public static List<Attribute> GetCustomAttributes(Type classType, string propertyName, Type attributeType)
        {
            List<Attribute> res = null;
            try
            {
                rwLock.EnterReadLock();
                if (!propertyCustomeAttributes.ContainsKey(classType))
                {
                    rwLock.ExitReadLock();
                    AddClassPropertiesandCustomAttributes(classType);
                    rwLock.EnterReadLock();
                }
                if (propertyCustomeAttributes.ContainsKey(classType) &&
                    propertyCustomeAttributes[classType].ContainsKey(propertyName) &&
                    propertyCustomeAttributes[classType][propertyName] != null)
                {
                    res = propertyCustomeAttributes[classType][propertyName].FindAll(a => a.GetType() == attributeType);
                }
            }
            finally
            {
                rwLock.ExitReadLock();
            }
            return res;
        }

        private static void AddClassPropertiesandCustomAttributes(Type classType)
        {
            try
            {
                rwLock.EnterWriteLock();
                propertyCustomeAttributes.Add(classType, new Dictionary<string, List<Attribute>>());
                foreach (PropertyInfo pInfo in classType.GetProperties())
                {
                    propertyCustomeAttributes[classType].Add(pInfo.Name, null);
                    var attribs = pInfo.GetCustomAttributes(false);
                    propertyCustomeAttributes[classType][pInfo.Name] = new List<Attribute>();
                    if (attribs != null)
                    {
                        foreach(var o in attribs)
                            propertyCustomeAttributes[classType][pInfo.Name].Add(o as Attribute);
                    }
                }
                foreach (FieldInfo pInfo in classType.GetFields())
                {
                    propertyCustomeAttributes[classType].Add(pInfo.Name, null);
                    var attribs = pInfo.GetCustomAttributes(false);
                    propertyCustomeAttributes[classType][pInfo.Name] = new List<Attribute>();
                    if (attribs != null)
                    {
                        foreach (var o in attribs)
                            propertyCustomeAttributes[classType][pInfo.Name].Add(o as Attribute);
                    }
                }
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
        }

    }
}
